﻿using System.Linq;
using System.Collections;
using System.Collections.Generic;

using Microsoft.Xna.Framework;
using System;

using Ah.Testudin.Engine.Utilities;

namespace Ah.Testudin.Engine.Events
{
    public class EventManager : IEventManager
    {
        #region Private Member Variables

        private Queue<ITestudinEvent> _eventQueue;

        private IDictionary<EventType, IList<TestudinEventHandler>> _eventHandlerMappings;

        private int? _maxMilliseconds;

        private int _currentMilliseconds;

        #endregion

        #region Constructors

        public EventManager(int? maxMilliseconds)
        {
            if (maxMilliseconds.HasValue && maxMilliseconds.Value < 1)
            {
                throw new ArgumentOutOfRangeException("maxMilliseconds");
            }

            _maxMilliseconds = maxMilliseconds;
        }

        #endregion

        #region IEventManager

        public void Initialize()
        {
            if (_eventQueue == null)
            {
                _eventQueue = new Queue<ITestudinEvent>();
            }
            else
            {
                _eventQueue.Clear();
            }

            if (_eventHandlerMappings == null)
            {
                _eventHandlerMappings = new Dictionary<EventType, IList<TestudinEventHandler>>();
            }

            _currentMilliseconds = 0;
        }

        public void RegisterEventHandler(
            EventType eventType, 
            TestudinEventHandler eventHandler)
        {
            if (_eventHandlerMappings.Keys.Contains(eventType))
            {
                _eventHandlerMappings[eventType].Add(eventHandler);
            }
            else
            {
                _eventHandlerMappings.Add(
                    new KeyValuePair<EventType, IList<TestudinEventHandler>>(
                        eventType,
                        new List<TestudinEventHandler>()
                        {
                            eventHandler
                        }));
            }
        }

        public void UnregisterEventHandler(
            EventType eventType,
            TestudinEventHandler eventHandler)
        {
            KeyValuePair<EventType, IList<TestudinEventHandler>> mapping = 
                _eventHandlerMappings.SingleOrDefault(
                    x => x.Key == eventType);

            if (mapping.Value != null)
            {
                if (mapping.Value.Contains(eventHandler))
                {
                    mapping.Value.Remove(eventHandler);
                }
            }
        }

        public void QueueEvent(ITestudinEvent testudinEvent)
        {
            if (testudinEvent is ActorDestroyedEvent)
            {
                int i = 1;
            }

            _eventQueue.Enqueue(testudinEvent);
        }

        public void TriggerEvent(ITestudinEvent testudinEvent)
        {
            KeyValuePair<EventType, IList<TestudinEventHandler>> eventHandlerMapping = 
                _eventHandlerMappings.SingleOrDefault(
                    x => x.Key == testudinEvent.EventType);

            if (!EqualityComparer<KeyValuePair<EventType, IList<TestudinEventHandler>>>.Default.Equals(
                eventHandlerMapping,
                default(KeyValuePair<EventType, IList<TestudinEventHandler>>)))
            {
                foreach (TestudinEventHandler currentEventHandler in eventHandlerMapping.Value)
                {
                    currentEventHandler(testudinEvent);
                }
            }
        }

        public void Update(GameTime gameTime)
        {
            Queue<ITestudinEvent> queueToProcess = new Queue<ITestudinEvent>(_eventQueue);

            _eventQueue.Clear();

            while (queueToProcess.Any())
            {
                if ((_maxMilliseconds.HasValue == false) || 
                    (_currentMilliseconds < _maxMilliseconds.Value))
                {
                    _currentMilliseconds += gameTime.ElapsedGameTime.Milliseconds;

                    ITestudinEvent currentEvent = queueToProcess.Dequeue();

                    TriggerEvent(currentEvent);
                }
                else
                {
                    _eventQueue = new Queue<ITestudinEvent>(
                        queueToProcess.Concat(
                            _eventQueue.AsEnumerable()));

                    _currentMilliseconds = 0;

                    break;
                }
            }
        }

        #endregion
    }
}
